/**
   Project: primeval compiler
   File: VirtualMachine.c
   Authors: Sebastian Zanner Matr.Nr.: 0855172
   E-Mail: sebastian.zanner@stud.sbg.ac.at
*/

#include <stdio.h>
#include <stdlib.h>
#include "VirtualMachine.h"

/*##############################################################################
#                                                                              #
# ENTRYPOINT                                                                   #
#                                                                              #
##############################################################################*/

int main(int argc, char **argv)  // 1 -> debug
{
    vm_debug = 0;
    if ( argc < 2 ) {
        vm_usageError();
        return 1;
    }
    char op, a, b;
    int c;

    vm_initialize();
    if ( vm_readExecutableFileToMemory(argv[1]) ) {
        vm_reg[GLOBALPOINTER] = vm_programSize + 4;
        while ( vm_run ) {
            vm_fetchInstruction();
            vm_decodeInstruction(&op, &a, &b, &c);
            vm_executeInstruction(&op, &a, &b, &c);
            if ( vm_debug ) {
                vm_printStatus();
            }
        }
        return 0;
    } else {
        return 1;
    }
}

void vm_usageError() {
    printf("\nPRIMEVAL: usage \"primevalvm <file> [d]\"\n");
}

/*##############################################################################
#                                                                              #
# INITIALIZE                                                                   #
#                                                                              #
##############################################################################*/
void vm_initialize() {
    vm_programCounter = 0;
    vm_memory = (unsigned char*) malloc(MEMORYSIZE * sizeof(char));
    vm_reg = (long*) malloc(REGISTERSIZE * sizeof(long));
    vm_run = TRUE;

    int i;
    for ( i = 0; i < REGISTERSIZE; i++ ) {
        vm_reg[i] = 0;
    }
    vm_reg[STACKPOINTER] = MEMORYSIZE - 1;
}

/*##############################################################################
#                                                                              #
# READ FILE TO MEMORY                                                          #
#                                                                              #
##############################################################################*/
int vm_readExecutableFileToMemory(char *fileName) {
    FILE *file = fopen(fileName, "rb");
    if ( !file ) {
        printf("PRIMEVAL> ERROR: cannot open file \"%s\"\n", fileName);
        return FALSE;
    }
    fseek(file, 0, SEEK_END);
    vm_programSize = ftell(file) - sizeof(unsigned long);
    fseek(file, 0, SEEK_SET);
    fread(vm_memory, 1, vm_programSize, file);
    fclose(file);
    return TRUE;
}

/*##############################################################################
#                                                                              #
# FETCH                                                                        #
#                                                                              #
##############################################################################*/
void vm_fetchInstruction() {
    vm_instructionRegister = 0;
    int i;
    for ( i = 3; i >= 0; i-- ) {
        vm_instructionRegister |= vm_memory[(vm_programCounter) + i];
        if ( i > 0 ) {
            vm_instructionRegister <<= 8;
    }   }
}

/*##############################################################################
#                                                                              #
# DECODE                                                                       #
#                                                                              #
##############################################################################*/
int vm_decodeInstruction(char *op, char *a, char *b, int *c) {

    *op = ( vm_instructionRegister >> 26 ) & 0x3F;

    if ( *op >= 1 && *op <= 33 ) {    // if Format 0 or Format 1
        *a = ( vm_instructionRegister >> 21 ) & 0x1F;
        *b = ( vm_instructionRegister >> 16 ) & 0x1F;

        if ( *op >= 1 && *op <= 22 ) {   // if Format 0
            *c = vm_instructionRegister & 0xFFFF;
            if ( *c >= 32768 ) {
                *c -= 65536;
            }
        }
        else {  // if Format 1
            *c = vm_instructionRegister & 0x1F;
        }
        return TRUE;
    }
    else if ( *op == 34 ) {   // if Format 2
        *c = vm_instructionRegister & 0x3FFFFFF;
        return TRUE;
    }
    else {
        return FALSE;
    }
}

/*##############################################################################
#                                                                              #
# EXECUTE                                                                      #
#                                                                              #
##############################################################################*/
void vm_executeInstruction(char *op, char *a, char *b, int *c) {

    if ( vm_debug ) {
        printf("\n%s(%d, %d, %d) @ %d -> REGISTER-STATUS:\n", vm_getInstructionString(*op),
                                                              *a, *b, *c , vm_programCounter/4);
    }

    int nextProgramCounter = vm_programCounter + 4;

    switch ( *op ) {
        case ADDI :
            vm_reg[(int) *a] = vm_reg[(int) *b] + *c;
            break;

        case SUBI :
            vm_reg[(int) *a] = vm_reg[(int) *b] - *c;
            break;

        case MULI :
            vm_reg[(int) *a] = vm_reg[(int) *b] * *c;
            break;

        case DIVI :
            vm_reg[(int) *a] = vm_reg[(int) *b] / *c;
            break;

        case MODI :
            vm_reg[(int) *a] = vm_reg[(int) *b] % *c;
            break;

        case CMPI :
            vm_reg[(int) *a] = vm_reg[(int) *b] - *c;
            break;

        case ADD :
            vm_reg[(int) *a] = vm_reg[(int) *b] + vm_reg[(int) *c];
            break;

        case SUB :
            vm_reg[(int) *a] = vm_reg[(int) *b] - vm_reg[(int) *c];
            break;

        case MUL :
            vm_reg[(int) *a] = vm_reg[(int) *b] * vm_reg[(int) *c];
            break;

        case Div :
            vm_reg[(int) *a] = vm_reg[(int) *b] / vm_reg[(int) *c];
            break;

        case Mod :
            vm_reg[(int) *a] = vm_reg[(int) *b] % vm_reg[(int) *c];
            break;

        case CMP :
            vm_reg[(int) *a] = vm_reg[(int) *b] - vm_reg[(int) *c];
            break;

        case LDW :
            temp = vm_memory[vm_reg[(int) *b] + *c];
            temp <<= 8;
            temp |= vm_memory[vm_reg[(int) *b] + *c-1];
            temp <<= 8;
            temp |= vm_memory[vm_reg[(int) *b] + *c-2];
            temp <<= 8;
            temp |= vm_memory[vm_reg[(int) *b] + *c-3];
            vm_reg[(int) *a] = temp;
            break;

        case STW :
            temp = vm_reg[(int) *a];
            vm_memory[vm_reg[(int) *b] + *c] = ( temp >> 24 ) & 0xFF;
            vm_memory[vm_reg[(int) *b] + *c-1] = ( temp >> 16 ) & 0xFF;
            vm_memory[vm_reg[(int) *b] + *c-2] = ( temp >> 8 ) & 0xFF;
            vm_memory[vm_reg[(int) *b] + *c-3] =( temp ) & 0xFF;
            break;

        case POP :
            temp = vm_memory[vm_reg[(int) *b]];
            temp <<= 8;
            temp |= vm_memory[vm_reg[(int) *b]-1];
            temp <<= 8;
            temp |= vm_memory[vm_reg[(int) *b]-2];
            temp <<= 8;
            temp |= vm_memory[vm_reg[(int) *b]-3];
            vm_reg[(int) *a] = temp;
            vm_reg[(int) *b] += *c;

            if ( vm_reg[STACKPOINTER] == MEMORYSIZE -1 )
            {
                vm_run = FALSE;
/*
            int i;
            for ( i = vm_reg[26] - 4; i < vm_reg[27]; i = i + 4) {
		printf("%d:%d %d:%d %d:%d %d:%d\n", i, vm_memory[i], i+1,vm_memory[i+1],i+2,vm_memory[i+2],i+3,vm_memory[i+3]);
		}*/
            }


            break;

        case PSH :
            vm_reg[(int) *b] -= *c;
            temp = vm_reg[(int) *a];
            vm_memory[vm_reg[(int) *b]] = ( temp >> 24 ) & 0xFF;
            vm_memory[vm_reg[(int) *b]-1] = ( temp >> 16 ) & 0xFF;
            vm_memory[vm_reg[(int) *b]-2] = ( temp >> 8 ) & 0xFF;
            vm_memory[vm_reg[(int) *b]-3] =( temp ) & 0xFF;
            break;

        case BEQ :
            if ( vm_reg[(int) *a] == 0 ) {
                nextProgramCounter = vm_programCounter + *c * 4;
            }
            break;

        case BGE :
            if ( vm_reg[(int) *a] >= 0 ) {
                nextProgramCounter = vm_programCounter + *c * 4;
            }
            break;

        case BGT :
            if ( vm_reg[(int) *a] > 0 ) {
                nextProgramCounter = vm_programCounter + *c * 4;
            }
            break;

        case BLE :
            if ( vm_reg[(int) *a] <= 0 ) {
               nextProgramCounter = vm_programCounter + *c * 4;
            }
            break;

        case BLT :
            if ( vm_reg[(int) *a] < 0 ) {
                nextProgramCounter = vm_programCounter + *c * 4;
            }
            break;

        case BNE :
            if ( vm_reg[(int) *a] != 0 ) {
                nextProgramCounter = vm_programCounter + *c * 4;
            }
            break;

        case BR :
            nextProgramCounter = vm_programCounter + *c * 4;
            break;

        case BSR :
            vm_reg[LINKREGISTER] = vm_programCounter + 4;
            nextProgramCounter = vm_programCounter + *c * 4;
            break;

        case RET :
            nextProgramCounter = vm_reg[(int) *c];
            break;

        case JSR :
            vm_reg[LINKREGISTER] = vm_programCounter + 4;
            nextProgramCounter = *c;
            break;

        case PRINTS :
            printf("PRIMEVAL> ");
            printString(vm_reg[(int) *a]);
            break;

        case PRINTC :
            printf("PRIMEVAL> %c\n", (char) vm_reg[(int) *a]);
            break;

        case READC :
            printf("PRIMEVAL> enter char ");
            char tempChar;
            scanf("%c", &tempChar);
            vm_reg[(int) *a] = (int) tempChar;
            break;

        case PRINTI :
            printf("PRIMEVAL> %d\n", (int) vm_reg[(int) *a]);
            break;

        case READI :
            printf("PRIMEVAL> enter integer ");
            scanf("%ld", &vm_reg[(int) *a]);
            break;

        default:
            vm_run = FALSE;
            printf("PRIMEVAL> ERROR: invalid opcode: %d\n", (int) *op);
    }

    *op = -1; *a = 0; *b = 0; *c = 0;
    if ( nextProgramCounter <= 0 ) {
        vm_programCounter = -1;
    } else {
        vm_programCounter = nextProgramCounter;
    }
}

void printString(long address)
{
    long curAddress = address+1 - 4;

    do
    {
	//printf("\nadd=%ld\n", curAddress);
        printf("%c", (char) vm_memory[curAddress]);
        curAddress = curAddress + 4;
    } while(vm_memory[curAddress] != '\0');
    printf("\n");
}

/*##############################################################################
#                                                                              #
# PRINTSTATUS                                                                  #
#                                                                              #
##############################################################################*/
void vm_printStatus() {
    int i;
    for ( i = 0; i < REGISTERSIZE - 1; i++ ) {
        printf("R[%d]=%ld; ", i, vm_reg[i]);
        if ( i % 8 == 0 && i != 0 ) {
            printf("\n");
        }
    }
    printf("R[%d]=%ld\n", REGISTERSIZE - 1, vm_reg[REGISTERSIZE - 1]);
}

char *vm_getInstructionString(char op) {
    switch ( op ) {
        case ADDI :
            return "ADDI";
        case SUBI :
            return "SUBI";
        case MULI :
            return "MULI";
        case DIVI :
            return "DIVI";
        case MODI :
            return "MODI";
        case CMPI :
            return "CMPI";
        case ADD :
            return "ADD";
        case SUB :
            return "SUB";
        case MUL :
            return "MUL";
        case Div :
            return "DIV";
        case Mod :
            return "MOD";
        case CMP :
            return "CMP";
        case LDW :
            return "LDW";
        case STW :
            return "STW";
        case POP :
            return "POP";
        case PSH :
            return "PSH";
        case BEQ :
            return "BEQ";
        case BGE :
            return "BGE";
        case BGT :
            return "BGT";
        case BLE :
            return "BLE";
        case BLT :
            return "BLT";
        case BNE :
            return "BNE";
        case BR :
            return "BR";
        case BSR :
            return "BSR";
        case RET :
            return "RET";
        case JSR :
            return "JSR";
        case PRINTC :
            return "PRINTC";
        case READC :
            return "READC";
        case PRINTI :
            return "PRINTI";
        case READI :
            return "READI";
        default:
            return "INVALID";
    }
}

